﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;

using Noris.Tools.TraceVisualiser.Support;

namespace Noris.Tools.TraceVisualiser.Components
{
    internal class TimeAxisRange : DblGraphControl
    {
        #region Konstrukce
        public TimeAxisRange()
        {
            this.Initialize();
        }
        private void Initialize()
        {
            this.SuspendLayout();

            this.TimeAxis = new TimeAxisCls();
            // this.TimeAxis.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
            this.TimeAxis.Location = new System.Drawing.Point(0, 0);
            this.TimeAxis.Margin = new System.Windows.Forms.Padding(0);
            this.TimeAxis.Size = new System.Drawing.Size(518, 38);
            this.TimeAxis.TabStop = false;
            this.TimeAxis.AxisChange += new TimeAxisChangeHandler(TimeAxis_AxisChange);

            this.BackColor = System.Drawing.SystemColors.ControlDark;
            this.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
            this.Controls.Add(this.TimeAxis);
            this.Size = new System.Drawing.Size(521, 77);

            this._CurrentVisibleRange = new Rectangle(50, 0, 100, 20);
            this._CalculateLogical(false);

            this.SetTimeAxisBounds();
            this._MouseInit();
            this.SizeChanged += new EventHandler(TimeAxisRange_SizeChanged);

            this._InitTimeAxisEvents();

            this.ResumeLayout(false);
        }
        void TimeAxis_AxisChange(object sender, TimeAxisChangeEventArgs e)
        {
            this._CalculateVisual(true);
        }
        void TimeAxisRange_SizeChanged(object sender, EventArgs e)
        {
            this.SetTimeAxisBounds();
        }
        private void SetTimeAxisBounds()
        {
            this.TimeAxis.Bounds = new Rectangle(this.ClientArea.Left, this.ClientArea.Top, this.ClientArea.Width, this.TimeAxis.Height);
        }
        private TimeAxisCls TimeAxis;
        #endregion
        #region Public property a eventy
        /// <summary>
        /// Časový rozsah grafu (první a poslední zobrazený okamžik).
        /// Je možno zapsat hodnotu = dojde k překreslení osy a návazným akcím (vyvolání eventu AxisChange())
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Description("Časový rozsah grafu (první a poslední zobrazený okamžik).")]
        [Browsable(true)]
        [Category("TimeAxis")]
        public TimeRange AxisVisibleTimeRange
        {
            get { return this.TimeAxis.AxisVisibleTimeRange; }
            set { this.TimeAxis.AxisVisibleTimeRange = value; }
        }
        /// <summary>
        /// Časový rozsah grafu (první a poslední zobrazený okamžik).
        /// Má vliv na vykreslování dat na ose a na HScrollBar.
        /// Je možno zapsat hodnotu = dojde k překreslení osy a návazným akcím (vyvolání eventu AxisChange())
        /// Při ukládání hodnoty se k požadavku přidají okraje.
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Description("Časový rozsah grafu (první a poslední zobrazený okamžik).")]
        [Browsable(true)]
        [Category("TimeAxis")]
        public TimeRange AxisRequestedTimeRange
        {
            get { return this.TimeAxis.AxisRequestedTimeRange; }
            set { this.TimeAxis.AxisRequestedTimeRange = value; }
        }
        /// <summary>
        /// Aktuální zvolený časový rozsah
        /// Je možno zapsat hodnotu = dojde k překreslení objektu a návazným akcím (vyvolání eventu TimeChange()).
        /// Vložení hodnoty vyvolá event CurrentTimeChange.
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Description("Aktuální zvolený časový rozsah")]
        [Browsable(true)]
        [Category("TimeAxis")]
        public TimeRange CurrentTimeRange
        {
            get { return this._CurrentTimeRange; }
            set { this._SetCurrentTimeRange(value); }
        }
        /// <summary>
        /// Výška části, která reprezentuje časovou osu (záhlaví)
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Description("Výška části, která reprezentuje časovou osu (záhlaví)")]
        [Browsable(true)]
        [Category("TimeAxis")]
        public int TimeAxisHeight
        {
            get { return this.TimeAxis.Height; }
            set { this.TimeAxis.Height = ToRange(value, 15, this.Height - 25); this.Refresh(); }
        }
        /// <summary>
        /// Výška části, která reprezentuje posuvník
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Description("Výška části, která reprezentuje posuvník")]
        [Browsable(true)]
        [Category("TimeAxis")]
        public int ScrollHeight
        {
            get { return this._ScrollHeight; }
            set { this._ScrollHeight = ToRange(value, 10, this.Height - this.TimeAxis.Bottom - 6); this.Refresh(); }
        }
        private int _ScrollHeight = 23;
        /// <summary>
        /// Výška části, která reprezentuje události pod posuvníkem. Může být 0.
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Description("Výška části, která reprezentuje události pod posuvníkem. Může být 0.")]
        [Browsable(true)]
        [Category("TimeAxis")]
        public int EventsHeight
        {
            get { return this._EventsHeight; }
            set { this._EventsHeight = ToRange(value, 0, this.Height - (this.TimeAxis.Bottom + this._ScrollHeight) - 6); this.Refresh(); }
        }
        private int _EventsHeight = 15;
        /// <summary>
        /// Kalkulátor časů a pozic
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Browsable(false)]
        public TimeAxisCalcCls TimeAxisCalculator { get { return this.TimeAxis.TimeAxisCalculator; } }
        /// <summary>
        /// Handler události Provádí se změna hodnoty CurrentTimeRange.
        /// Volá se po každé jednotlivé změně.
        /// Po dokončení změn bude volána událost this.CurrentTimeChanged.
        /// </summary>
        [Browsable(true)]
        [Category("TimeAxis")]
        [Description("Handler události Provádí se změna hodnoty CurrentTimeRange. Volá se po každé jednotlivé změně.Po dokončení změn bude volána událost this.CurrentTimeChanged.")]
        public event EventHandler CurrentTimeChanging;
        /// <summary>
        /// Handler události Proběhla změna hodnoty CurrentTimeRange.
        /// Volá se až po dokončení změny.
        /// </summary>
        [Browsable(true)]
        [Category("TimeAxis")]
        [Description("Handler události Proběhla změna hodnoty CurrentTimeRange. Volá se až po dokončení změny.")]
        public event EventHandler CurrentTimeChanged;
        protected virtual void OnCurrentTimeChanging()
        {
            if (this.CurrentTimeChanging != null)
                this.CurrentTimeChanging(this, EventArgs.Empty);
        }
        protected virtual void OnCurrentTimeChanged()
        {
            if (this.CurrentTimeChanged != null)
                this.CurrentTimeChanged(this, EventArgs.Empty);
        }
        private static int ToRange(int value, int min, int max)
        {
            return (value < min ? min : (value > max ? max : value));
        }
        #endregion
        #region TimeAxisEvents
        private void _InitTimeAxisEvents()
        {
            this._TimeEventList = new List<_TimeAxisEvent>();
        }
        internal void ClearTimeAxisEvents()
        {
            this._TimeEventList.Clear();
        }
        internal void AddTimeAxisEvent(TimeAxisEventInfo eventInfo)
        {
            this._AddTimeAxisEvent(eventInfo);
        }
        internal void AddTimeAxisEvent(DateTime time, Image image)
        {
            TimeAxisEventInfo eventInfo = new TimeAxisEventInfo(time, image);
            this._AddTimeAxisEvent(eventInfo);
        }
        internal void AddTimeAxisEvent(DateTime time, Image image, string toolTipText)
        {
            TimeAxisEventInfo eventInfo = new TimeAxisEventInfo(time, image, toolTipText);
            this._AddTimeAxisEvent(eventInfo);
        }
        internal void AddTimeAxisEvent(TimeRange time, Color backColor)
        {
            TimeAxisEventInfo eventInfo = new TimeAxisEventInfo(time, backColor);
            this._AddTimeAxisEvent(eventInfo);
        }
        internal void AddTimeAxisEvent(TimeRange time, Color backColor, string toolTipText)
        {
            TimeAxisEventInfo eventInfo = new TimeAxisEventInfo(time, backColor, toolTipText);
            this._AddTimeAxisEvent(eventInfo);
        }
        private void _AddTimeAxisEvent(TimeAxisEventInfo eventInfo)
        {
            _TimeAxisEvent axisEvent = new _TimeAxisEvent(eventInfo);
            this._TimeEventList.Add(axisEvent);
            this._TimeEventList.Sort((a, b) => a.EventInfo.Time.Begin.CompareTo(b.EventInfo.Time.Begin));
            this._SetTimeAxisEventBound(axisEvent);
        }
        private void _SetTimeAxisEventsBound()
        {
            if (this._TimeEventList == null) return;
            foreach (_TimeAxisEvent axisEvent in this._TimeEventList)
                this._SetTimeAxisEventBound(axisEvent);
        }
        private void _SetTimeAxisEventBound(_TimeAxisEvent axisEvent)
        {
            TimeRange timeRange = axisEvent.EventInfo.Time;
            int begin = this.TimeAxis.TimeAxisCalc.GetPixelForTime(timeRange.Begin); // this.TimeAxis.TimeAxisCalc.GetVisualForTime(timeRange.Begin);
            int end = (timeRange.Seconds <= 0d ? begin : this.TimeAxis.TimeAxisCalc.GetPixelForTime(timeRange.End));
            axisEvent.TimeBounds = new Rectangle(this.TimeAxis.Left + begin, 0, (end - begin), this.Height);
        }
        private List<_TimeAxisEvent> _TimeEventList;
        private class _TimeAxisEvent
        {
            internal _TimeAxisEvent(TimeAxisEventInfo eventInfo)
            {
                this.EventInfo = eventInfo;
            }
            internal TimeAxisEventInfo EventInfo;
            internal Rectangle TimeBounds;
            internal Rectangle InteractiveBounds;
        }
        #endregion
        #region Časové přepočty
        /// <summary>
        /// Daný interval vloží do this._CurrentTimeRange.
        /// Pokud je jiný než stávající, vyvolá překreslení a spustí event CurrentTimeChange.
        /// </summary>
        /// <param name="timeRange"></param>
        private void _SetCurrentTimeRange(TimeRange timeRange)
        {
            if (timeRange.Begin != this._CurrentTimeRange.Begin || timeRange.End != this._CurrentTimeRange.End)
            {
                this._CurrentTimeRange = timeRange;
                this._CalculateVisual(true);
                this.OnCurrentTimeChanging();
                this.OnCurrentTimeChanged();
            }
        }
        /// <summary>
        /// Určí vizuální souřadnice _CurrentVisibleRange podle logické hodnoty _CurrentTimeRange.
        /// Podle požadavku (runPaint) může vyvolat Refresh() = repaint.
        /// </summary>
        private void _CalculateVisual(bool runPaint)
        {
            TimeRange timeRange = this._CurrentTimeRange;
            int begin = this.TimeAxis.TimeAxisCalc.GetPixelForTime(timeRange.Begin); // this.TimeAxis.TimeAxisCalc.GetVisualForTime(timeRange.Begin);
            int end = (timeRange.Seconds <= 0d ? begin : this.TimeAxis.TimeAxisCalc.GetPixelForTime(timeRange.End));
            this._CurrentVisibleRange = new Rectangle(this.TimeAxis.Left + begin, 0, (end - begin), this.Height);
            this._SetTimeAxisEventsBound();
            if (runPaint)
                this.Refresh();
        }
        /// <summary>
        /// Určí logickou hodnotu _CurrentTimeRange podle vizuální souřadnice _CurrentVisibleRange.
        /// Podle požadavku (runEvent) může vyvolat OnCurrentTimeChange() = event.
        /// </summary>
        private void _CalculateLogical(bool runEvent)
        {
            RectangleF bounds = this._CurrentVisibleRange;
            DateTime begin = this.TimeAxis.TimeAxisCalc.GetTimeForVisual(bounds.X);
            DateTime end = (bounds.Width <= 0f ? begin : this.TimeAxis.TimeAxisCalc.GetTimeForVisual(bounds.X + bounds.Width));
            if (begin != this._CurrentTimeRange.Begin || end != this._CurrentTimeRange.End)
            {
                this._CurrentTimeRange = new TimeRange(begin, end);
                if (runEvent)
                    this.OnCurrentTimeChanging();
            }
        }
        private TimeRange _BeginEditTimeRange;
        private TimeRange _CurrentTimeRange;
        /// <summary>
        /// Teoretická oblast posuvníku: platné souřadnice jsou jen v ose X
        /// </summary>
        private Rectangle _CurrentVisibleRange;
        /// <summary>
        /// Oblast, ve které je vykreslen posuvník
        /// </summary>
        private Rectangle _CurrentActiveArea;
        /// <summary>
        /// Oblast, která představuje scrollbar = kde se může pohybovat posuvník
        /// </summary>
        private Rectangle _CurrentScrollArea;
        /// <summary>
        /// Oblast, kde jsou zobrazeny události (pod posuvníkem)
        /// </summary>
        private Rectangle _CurrentEventsArea;
        #endregion
        #region Paint
        protected override void OnPaintToBuffer(object sender, PaintEventArgs e)
        {
            base.OnPaintToBuffer(sender, e);

            System.Windows.Forms.VisualStyles.ScrollBarState sbState = System.Windows.Forms.VisualStyles.ScrollBarState.Normal;
            if (this.CurrentMouseSegment == MouseSegmentType.LeftEdge || this.CurrentMouseSegment == MouseSegmentType.Body || this.CurrentMouseSegment == MouseSegmentType.RightEdge)
            {
                sbState = System.Windows.Forms.VisualStyles.ScrollBarState.Hot;
                if (this.CurrentMouseDownPoint.HasValue)
                    sbState = System.Windows.Forms.VisualStyles.ScrollBarState.Pressed;
            }
            this.PaintCreateAreas();

            bool isRenderer = ScrollBarRenderer.IsSupported;
            if (isRenderer)
                this.PaintWithRenderer(e, sbState);
            else
                this.PaintWithRectangle(e, sbState);

            this.PaintAxisEvents(e);
        }
        private void PaintCreateAreas()
        {
            int y1 = this.TimeAxis.Bottom + 2;
            int y2 = y1 + 0;
            int y3 = y2 + this._ScrollHeight;
            int y4 = y3 + 0;
            int y5 = y4 + 3;
            int y6 = y5 + this._EventsHeight;

            this._CurrentScrollArea = new Rectangle(this.TimeAxis.Left, y1, this.TimeAxis.Width, y4 - y1);

            int x1 = this._CurrentVisibleRange.X - 1;
            int w1 = this._CurrentVisibleRange.Width + 2;
            if (w1 < 1) w1 = 1;
            this._CurrentActiveArea = new Rectangle(x1, y2, w1, y3 - y2);

            this._CurrentEventsArea = new Rectangle(this._CurrentScrollArea.X, y5, this._CurrentScrollArea.Width, y6 - y5);
        }
        private void PaintWithRenderer(PaintEventArgs e, System.Windows.Forms.VisualStyles.ScrollBarState sbState)
        {
            Rectangle area = this._CurrentActiveArea;
            ScrollBarRenderer.DrawLeftHorizontalTrack(e.Graphics, this._CurrentScrollArea, sbState);
            ScrollBarRenderer.DrawHorizontalThumb(e.Graphics, area, sbState);

            if (area.Width > 18)
                PaintArrow(e, sbState, this._CurrentScrollArea, area);

            if (area.Width > (ScrollBarRenderer.GetThumbGripSize(e.Graphics, sbState).Width + 4))
                ScrollBarRenderer.DrawHorizontalThumbGrip(e.Graphics, area, sbState);
        }
        private void PaintWithRectangle(PaintEventArgs e, System.Windows.Forms.VisualStyles.ScrollBarState sbState)
        {
            Rectangle area = this._CurrentActiveArea;
            using (Brush brush = PaintGetBackBrush(sbState, this._CurrentScrollArea))
            {
                e.Graphics.FillRectangle(brush, this._CurrentScrollArea);
            }

            Rectangle rect = area;
            rect.Y += 1;
            rect.Height -= 2;
            
            GraphicsPath path = new GraphicsPath();
            int x1 = rect.X;
            int x2 = rect.Right - 1;
            int y1 = rect.Y;
            int y2 = rect.Bottom - 1;
            path.AddPolygon(new Point[] {
                new Point(x1, y1 + 1),
                new Point(x1 + 1, y1),
                new Point(x2 - 1, y1),
                new Point(x2, y1 + 1),
                new Point(x2, y2 - 1),
                new Point(x2 - 1, y2),
                new Point(x1 + 1, y2),
                new Point(x1, y2 - 1),
                new Point(x1, y1 + 1)});
            path.CloseFigure();
            using (Brush brush = PaintGetAreaBrush(sbState, rect))
            {
                e.Graphics.FillPath(brush, path);
                e.Graphics.DrawPath(SystemPens.ControlDarkDark, path);
            }

            if (area.Width > 18)
                PaintArrow(e, sbState, this._CurrentScrollArea, area);
        }
        private Brush PaintGetAreaBrush(System.Windows.Forms.VisualStyles.ScrollBarState sbState, Rectangle area)
        {
            LinearGradientBrush lgb;
            Color dark = SystemColors.ControlLight;
            Color midd = SystemColors.ControlLight.Morph(SystemColors.HotTrack, 0.15f);
            Color hot = SystemColors.ControlLight.Morph(SystemColors.HotTrack, 0.25f);
            switch (sbState)
            {
                case System.Windows.Forms.VisualStyles.ScrollBarState.Normal:
                    lgb = new LinearGradientBrush(area, dark, midd, 90f);
                    return lgb;
                case System.Windows.Forms.VisualStyles.ScrollBarState.Hot:
                    lgb = new LinearGradientBrush(area, dark, midd, 90f);
                    return lgb;
                case System.Windows.Forms.VisualStyles.ScrollBarState.Pressed:
                    lgb = new LinearGradientBrush(area, dark, hot, -90f);
                    return lgb;
            }
            return new SolidBrush(dark);
        }
        private Brush PaintGetBackBrush(System.Windows.Forms.VisualStyles.ScrollBarState sbState, Rectangle area)
        {
            LinearGradientBrush lgb;
            Color dark =  SystemColors.ControlDark;
            Color midd = SystemColors.ControlDark.Morph(SystemColors.Control, 0.10f);
            Color hot =  SystemColors.ControlDark.Morph(SystemColors.HotTrack, 0.10f);
            switch (sbState)
            {
                case System.Windows.Forms.VisualStyles.ScrollBarState.Normal:
                    lgb = new LinearGradientBrush(area, dark, midd, 90f);
                    return lgb;
                case System.Windows.Forms.VisualStyles.ScrollBarState.Hot:
                    lgb = new LinearGradientBrush(area, dark, hot, -90f);
                    return lgb;
                case System.Windows.Forms.VisualStyles.ScrollBarState.Pressed:
                    lgb = new LinearGradientBrush(area, dark, hot, -90f);
                    return lgb;
            }
            return new SolidBrush(dark);
        }
        private void PaintArrow(PaintEventArgs e, System.Windows.Forms.VisualStyles.ScrollBarState sbState, Rectangle back, Rectangle area)
        {
            int y = area.Y + area.Height / 2;
            int x1 = area.X + 2;
            int x2 = area.X + area.Width / 2;
            int x3 = area.X + area.Width - 3;

            Color colorBegin = PaintGetArrowColor(SystemColors.ControlLight, sbState);
            Color colorEnd = colorBegin.Morph(Color.DarkBlue, 0.25f);

            var state = e.Graphics.Save();
            this.PaintArrow(e, sbState, x2, x1, y, colorBegin, colorEnd);
            this.PaintArrow(e, sbState, x2, x3, y, colorBegin, colorEnd);
            e.Graphics.Restore(state);
        }
        private void PaintArrow(PaintEventArgs e, System.Windows.Forms.VisualStyles.ScrollBarState sbState, int begin, int end, int y, Color colorBegin, Color colorEnd)
        {
            Arrow arrow = new Arrow();
            arrow.Begin = new PointD(begin, y);
            arrow.End = new PointD(end, y);
            arrow.ArrowLength = 4d;
            arrow.ArrowOverhung = 6d;
            arrow.ArrowSlant = 2d;
            arrow.WidthArrow = 0d;
            arrow.Width = 3d;
            GraphicsPath path = arrow.Path;
            Arrow.PrepareGraphics(e.Graphics);
            using (Brush brush = arrow.GetBrushSlant(colorBegin, colorEnd))
            {
                e.Graphics.FillPath(brush, path);
            }
        }
        private Color PaintGetArrowColor(Color color, System.Windows.Forms.VisualStyles.ScrollBarState sbState)
        {
            switch (sbState)
            {
                case System.Windows.Forms.VisualStyles.ScrollBarState.Normal:
                    return color.Morph(Color.Blue, 0.15f);
                case System.Windows.Forms.VisualStyles.ScrollBarState.Hot:
                    return color.Morph(Color.Blue, 0.25f);
                case System.Windows.Forms.VisualStyles.ScrollBarState.Pressed:
                    return color.Morph(Color.Blue, 0.45f);
            }
            return color;
        }
        private Color PaintModifyColor(Color color, System.Windows.Forms.VisualStyles.ScrollBarState sbState)
        {
            switch (sbState)
            {
                case System.Windows.Forms.VisualStyles.ScrollBarState.Normal:
                    return color;
                case System.Windows.Forms.VisualStyles.ScrollBarState.Hot:
                    return color.Morph(Color.LightYellow, 0.25f);
                case System.Windows.Forms.VisualStyles.ScrollBarState.Pressed:
                    return color.Morph(Color.DarkBlue, 0.10f);
            }
            return color;
        }
        private void PaintAxisEvents(PaintEventArgs e)
        {
            int top = this._CurrentEventsArea.Top;
            int bottom = this._CurrentEventsArea.Bottom;
            if (this._TimeEventList == null) return;
            if (this._TimeEventList.Count == 0) return;
            foreach (_TimeAxisEvent axisEvent in this._TimeEventList)
            {
                if (axisEvent.EventInfo.Image != null)
                {
                    int x = axisEvent.TimeBounds.X + axisEvent.TimeBounds.Width / 2;
                    int w = axisEvent.EventInfo.Image.Width;
                    int h = axisEvent.EventInfo.Image.Height;
                    int y = bottom - h;
                    if (y < top) y = top;
                    Rectangle area = new Rectangle(x - w / 2, y, w, h);
                    e.Graphics.DrawImage(axisEvent.EventInfo.Image, area);
                    axisEvent.InteractiveBounds = area;
                }
            }
        }
        #endregion
        #region Interaktivita (myš)
        private void _MouseInit()
        {
            this.MouseEnter += new EventHandler(_MouseEnter);
            this.MouseMove += new MouseEventHandler(_MouseMove);
            this.MouseDown += new MouseEventHandler(_MouseDown);
            this.MouseUp += new MouseEventHandler(_MouseUp);
            this.MouseLeave += new EventHandler(_MouseLeave);
            this.CurrentMouseSegment = MouseSegmentType.None;
            this.CurrentTimeEvent = null;

            // ToolTip je poskytován zdarma, a to díky rodičovské třídě :-)
            this.ToolTipSet(false, SystemColors.Info.Morph(Color.LightGray, 0.25f), Color.Black, ToolTipIcon.None, "Informace");
            this.ToolTipSet(500, 750, 10000, 750, true);
        }
        private void _MouseEnter(object sender, EventArgs e)
        {
            this.CurrentMouseSegment = MouseSegmentType.None;
            this.CurrentMouseDownPoint = null;
            this.CurrentMouseDragOffset = null;
        }
        private void _MouseLeave(object sender, EventArgs e)
        {
            this.CurrentMouseSegment = MouseSegmentType.None;
            this.SetCursor(true);
        }
        private void _MouseMove(object sender, MouseEventArgs e)
        {
            if (!this.CurrentMouseDownPoint.HasValue)
                this._MouseMoveOver(e);
            else
                this._MouseMoveDrag(e);
        }
        private void _MouseMoveOver(MouseEventArgs e)
        {
            MouseSegmentType mouseSegment = _MouseGetSegment(e.Location);
            if (mouseSegment == MouseSegmentType.Events)
                this._MouseMoveOverEvents(e);
            else
                this.CurrentTimeEvent = null;

            if (mouseSegment == this.CurrentMouseSegment) return;

            this.CurrentMouseSegment = mouseSegment;
            this.CurrentMouseButton = e.Button;
            this.SetCursor(true);
        }
        private void _MouseMoveOverEvents(MouseEventArgs e)
        {
            _TimeAxisEvent timeEvent = null;
            for (int i = this._TimeEventList.Count - 1; i >= 0; i--)
            {
                if (this._TimeEventList[i].InteractiveBounds.Contains(e.Location))
                {
                    timeEvent = this._TimeEventList[i];
                    break;
                }
            }
            if (!Object.ReferenceEquals(timeEvent, this.CurrentTimeEvent))
            {
                if (timeEvent == null)
                    this.ToolTipHide();
                else
                {
                    string toolTipText = timeEvent.EventInfo.ToolTipText;
                    if (!String.IsNullOrEmpty(toolTipText))
                        this.ToolTip.SetToolTip(this, toolTipText);
                }
                this.CurrentTimeEvent = timeEvent;
            }
        }
        private void _MouseMoveDrag(MouseEventArgs e)
        {
            if (!this.CurrentMouseDragOffset.HasValue)
                this._MouseMoveDragTest(e);
            else
                this._MouseMoveDragRun(e);
        }
        private void _MouseDown(object sender, MouseEventArgs e)
        {
            this.CurrentMouseDragOffset = null;
            this.CurrentMouseDownPoint = e.Location;
            this.CurrentKeyModifiersOnMouseDown = Control.ModifierKeys;
            this.Refresh();
        }
        private void _MouseUp(object sender, MouseEventArgs e)
        {
            if (!this.CurrentMouseDragOffset.HasValue)
                this._MouseUpClick(e);
            else
                this._MouseMoveDragDone(e);
            
            this.CurrentMouseDownPoint = null;
            this.Refresh();
        }
        private void _MouseUpClick(MouseEventArgs e)
        {
            if (_LastMouseUpTime.HasValue && _LastMouseUpTime.Value > (DateTime.Now.AddMilliseconds((double)(-SystemInformation.DoubleClickTime))))
            {
                this.OnMouseDoubleClick(e);
                _LastMouseUpTime = null;
            }
            else
            {
                this.OnMouseClick(e);
                _LastMouseUpTime = DateTime.Now;
            }
        }
        private DateTime? _LastMouseUpTime;
        private void _MouseMoveDragTest(MouseEventArgs e)
        {
            Size distance = new System.Drawing.Size(Math.Abs(e.Location.X - this.CurrentMouseDownPoint.Value.X), Math.Abs(e.Location.Y - this.CurrentMouseDownPoint.Value.Y));
            Size dragSize = SystemInformation.DragSize;
            if (distance.Width <= (dragSize.Width / 2) && distance.Height <= (dragSize.Height / 2))
                return;

            this._MouseMoveDragBegin(e);
        }
        private void _MouseMoveDragBegin(MouseEventArgs e)
        {
            this._BeginEditTimeRange = this._CurrentTimeRange;
            int x = this._CurrentActiveArea.X + (this.CurrentMouseSegment == MouseSegmentType.RightEdge ? this._CurrentActiveArea.Width : 0);
            int y = this._CurrentActiveArea.Y + (this.CurrentMouseSegment == MouseSegmentType.BottomEdge ? this._CurrentActiveArea.Height : 0);
            this.CurrentMouseDragOffset = new Size(this.CurrentMouseDownPoint.Value.X - x, this.CurrentMouseDownPoint.Value.Y - y);
            this._MouseMoveDragRun(e);
        }
        private void _MouseMoveDragRun(MouseEventArgs e)
        {
            bool viaPixel = false;
            if (viaPixel)
                this._MouseMoveDragRunVisual(e);
            else
                this._MouseMoveDragRunLogical(e);
        }
        private void _MouseMoveDragRunVisual(MouseEventArgs e)
        {
            Rectangle curr = this._CurrentActiveArea;
            Rectangle drag = curr;
            switch (this.CurrentMouseSegment)
            {
                case MouseSegmentType.Body:
                    drag.X = e.X - this.CurrentMouseDragOffset.Value.Width;
                    break;
                case MouseSegmentType.LeftEdge:
                    drag.X = e.X - this.CurrentMouseDragOffset.Value.Width;
                    drag.Width = curr.X + curr.Width - drag.X;
                    break;
                case MouseSegmentType.RightEdge:
                    drag.Width = e.X - this.CurrentMouseDragOffset.Value.Width - curr.X;
                    break;
            }
            if (drag != curr)
            {
                this._CurrentVisibleRange = drag;
                this._CalculateLogical(true);
                this.OnCurrentTimeChanging();
                this.Refresh();
            }
        }
        private void _MouseMoveDragRunLogical(MouseEventArgs e)
        {
            TimeRange curr = this._BeginEditTimeRange;
            TimeRange drag = curr;
            int x = e.X - this.CurrentMouseDragOffset.Value.Width;
            Keys keys = Control.ModifierKeys;
            bool isCtrl = ((keys& Keys.Control) != 0);
            TimeAxisTickType tick = (isCtrl ? TimeAxisTickType.SmallDividerTick : TimeAxisTickType.None);
            DateTime d = this.TimeAxis.TimeAxisCalc.GetTimeForVisual(x, tick);
            switch (this.CurrentMouseSegment)
            {
                case MouseSegmentType.Body:
                    drag.MoveToBegin(d);
                    break;
                case MouseSegmentType.LeftEdge:
                    drag.Begin = d;
                    break;
                case MouseSegmentType.RightEdge:
                    drag.End = d;
                    break;
                case MouseSegmentType.LeftTrack:
                case MouseSegmentType.RightTrack:
                    x = this.CurrentMouseDownPoint.Value.X;
                    DateTime d1 = this.TimeAxis.TimeAxisCalc.GetTimeForVisual(x, tick);
                    DateTime d2 = this.TimeAxis.TimeAxisCalc.GetTimeForVisual(e.X, tick);
                    drag = new TimeRange(d1, d2);
                    break;
            }
            if (drag.Seconds < 0d)
                drag.Swap();

            if (drag != curr)
            {
                this._CurrentTimeRange = drag;
                this.OnCurrentTimeChanging();
                this._CalculateVisual(true);
            }
        }
        private void _MouseMoveDragDone(MouseEventArgs e)
        {
            if (this._CurrentTimeRange != this._BeginEditTimeRange)
                this.OnCurrentTimeChanged();
        }
        private void SetCursor(bool reDraw)
        {
            switch (this.CurrentMouseSegment)
            {
                case MouseSegmentType.None:
                case MouseSegmentType.Body:
                    this.Cursor = Cursors.Default;
                    break;
                case MouseSegmentType.TopEdge:
                case MouseSegmentType.BottomEdge:
                    this.Cursor = Cursors.HSplit;
                    break;
                case MouseSegmentType.LeftEdge:
                case MouseSegmentType.RightEdge:
                    this.Cursor = Cursors.VSplit;
                    break;
                default:
                    this.Cursor = Cursors.Default;
                    break;
            }
            if (reDraw)
                this.Refresh();
        }
        private MouseSegmentType _MouseGetSegment(Point point)
        {
            if (this._CurrentScrollArea.Contains(point))
                return _MouseGetSegmentInScrollArea(point);
            if (this._CurrentEventsArea.Contains(point))
                return MouseSegmentType.Events;
            return MouseSegmentType.None;
        }
        private MouseSegmentType _MouseGetSegmentInScrollArea(Point point)
        {
            Rectangle area = this._CurrentActiveArea;

            int y = point.Y;
            if (y < area.Y || y > area.Bottom) return MouseSegmentType.None;

            int x = point.X;
            int w = area.Width;
            if (w < 2) w = 2;
            int aw = 3;
            int x1 = area.X - aw;
            int x2 = area.X + aw;
            int x3 = area.X + w - aw;
            if ((x3 - x2) <= 2)
            {
                int xc = area.X + (w / 2);
                x2 = xc - 1;
                x3 = xc + 1;
            }
            int x4 = area.X + w + aw;

            if (x < x1) return MouseSegmentType.LeftTrack;
            if (x > x4) return MouseSegmentType.RightTrack;
            if (x >= x3) return MouseSegmentType.RightEdge;
            if (x < x2) return MouseSegmentType.LeftEdge;
            return MouseSegmentType.Body;
        }
        /// <summary>
        /// Segment objektu, nad kterým se nachází myš, nebo který je myší popotahován
        /// </summary>
        private MouseSegmentType CurrentMouseSegment;
        /// <summary>
        /// Buttony myši, které se zaregistrovaly v okamžiku MouseDown
        /// </summary>
        private MouseButtons CurrentMouseButton;
        /// <summary>
        /// Klávesy Ctrl+Alt+Del, které byly stisknuty v okamžiku MouseDown
        /// </summary>
        private Keys CurrentKeyModifiersOnMouseDown;
        private bool CurrentKeyModifiersCtrl { get { return ((this.CurrentKeyModifiersOnMouseDown & Keys.Control) != 0); } }
        private bool CurrentKeyModifiersAlt { get { return ((this.CurrentKeyModifiersOnMouseDown & Keys.Alt) != 0); } }
        private bool CurrentKeyModifiersShift { get { return ((this.CurrentKeyModifiersOnMouseDown & Keys.Shift) != 0); } }
        /// <summary>
        /// Bod, na kterém se myš stiskla (v koordinátech controlu this).
        /// Pokud je null, pak myš není zmáčknutá.
        /// </summary>
        private Point? CurrentMouseDownPoint;
        /// <summary>
        /// Offset pozice myši oproti pozici {0;0} objektu, který je myší popotahován (viz this.CurrentMouseSegment)
        /// Pokud je null, pak myš ještě nezačala nic přesouvat (možná není zmáčknutá, anebo je zmáčknutá ale nachází se blízko bodu zmáčknutí).
        /// </summary>
        private Size? CurrentMouseDragOffset;
        /// <summary>
        /// Událost pod myší, detekujeme změny
        /// </summary>
        private _TimeAxisEvent CurrentTimeEvent;
        private enum MouseSegmentType
        {
            None = 0,
            LeftTrack,
            LeftEdge,
            TopEdge,
            Body,
            RightEdge,
            BottomEdge,
            RightTrack,
            Events
        }
        #endregion
    }
    #region TimeAxisEventInfo : Třída, která umožní zobrazit další data na časové ose
    /// <summary>
    /// TimeAxisEventInfo : Třída, která umožní zobrazit další data na časové ose
    /// </summary>
    internal class TimeAxisEventInfo
    {
        internal TimeAxisEventInfo(DateTime time, Image image)
        {
            this.Time = new TimeRange(time, time);
            this.Image = image;
        }
        internal TimeAxisEventInfo(DateTime time, Image image, string toolTipText)
        {
            this.Time = new TimeRange(time, time);
            this.Image = image;
            this.ToolTipText = toolTipText;
        }
        internal TimeAxisEventInfo(TimeRange time, Color backColor)
        {
            this.Time = time;
            this.BackColor = backColor;
        }
        internal TimeAxisEventInfo(TimeRange time, Color backColor, string toolTipText)
        {
            this.Time = time;
            this.BackColor = backColor;
            this.ToolTipText = toolTipText;
        }
        internal TimeRange Time { get; set; }
        internal Image Image { get; set; }
        internal Color BackColor { get; set; }
        internal string ToolTipText { get; set; }
    }
    #endregion
}
